package pl.edu.agh.neuraleconomy.core.nn;

import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;
import org.encog.ml.data.MLData;
import org.encog.ml.data.basic.BasicMLData;

import pl.edu.agh.neuraleconomy.common.utils.DateUtils;
import pl.edu.agh.neuraleconomy.core.nn.transformer.ClosingPriceTransformer;
import pl.edu.agh.neuraleconomy.core.nn.transformer.IDataTransformer;
import pl.edu.agh.neuraleconomy.model.exchange.Exchange;
import pl.edu.agh.neuraleconomy.persistence.base.DaoProvider;
import pl.edu.agh.neuraleconomy.persistence.exchange.ExchangeDao;

public class NeuralNetworkService {
	
	private Logger logger = Logger.getLogger(getClass());

	public List<Exchange> getTrainData(Long companyId, Date dateTo) {
		logger.debug(String.format("Receiving train data for: %s", DateUtils.formatDate(dateTo)));
		ExchangeDao dao = DaoProvider.getExchangeDao();
		return dao.getLatestByCompanyExcludeDate(companyId, dateTo, (long) 50);
	}

	public void trainNetworkWithData(SimpleNetwork network, Long companyId, Date dateTo, IDataTransformer transformer) {
		trainNetworkWithData(network, getTrainData(companyId, dateTo), transformer);
	}

	public void trainNetworkWithData(SimpleNetwork network, Long companyId, Date dateTo) {
		trainNetworkWithData(network, companyId, dateTo, new ClosingPriceTransformer());
	}

	public void trainNetworkWithData(SimpleNetwork network, double[] data) {
		logger.debug(String.format("Training with data: %s", Arrays.toString(data)));
		NetworkTrainer trainer = new NetworkTrainer();
		trainer.setMaxIterations(1000);

		trainer.setNetwork(network);
		trainer.setDataSet(CoreUtils.createDataSet(data, network.getInput(), network.getOutput()));

		trainer.train();
	}

	public void trainNetworkWithData(SimpleNetwork network, Collection<Exchange> exchanges) {
		trainNetworkWithData(network, CoreUtils.toDoubleArray(exchanges));
	}

	public void trainNetworkWithData(SimpleNetwork network, double[] data, IDataTransformer transformer) {
		double transformed[] = transformer.transform(data);
		trainNetworkWithData(network, transformed);
	}

	public void trainNetworkWithData(SimpleNetwork network, Collection<Exchange> exchanges, IDataTransformer transformer) {
		trainNetworkWithData(network, CoreUtils.toDoubleArray(exchanges), transformer);
	}

	public double[] predictForCompany(SimpleNetwork network, Long companyId, Date dateFrom, int sessions) {
		return predictForCompany(network, companyId, dateFrom, sessions, new ClosingPriceTransformer(), false);
	}

	public double[] predictForCompany(SimpleNetwork network, Long companyId, Date dateFrom, int sessions, IDataTransformer transformer,
			boolean revertTransformation) {
		ExchangeDao dao = DaoProvider.getExchangeDao();

		List<Exchange> exchanges = dao.getLatestByCompanyExcludeDate(companyId, dateFrom,
				(long) transformer.getInputLenForOutputLen(network.getInput()));
		double data[] = CoreUtils.toDoubleArray(exchanges);

		return predictForCompany(network, data, sessions, transformer, revertTransformation);
	}

	public double[] predictForCompany(SimpleNetwork network, double[] data, int sessions, IDataTransformer transformer,
			boolean revertTransformation) {

		double[] transformedData = transformer.transform(data);
		logger.debug(String.format("Predicting with input: %s", Arrays.toString(transformedData)));

		double[] res = new double[sessions];
		double[] temp = Arrays.copyOf(transformedData, transformedData.length + sessions);

		for (int i = 0; i < sessions; i++) {
			MLData inputData = new BasicMLData(Arrays.copyOfRange(temp, i, i + network.getInput()));
			MLData result = network.getNetwork().compute(inputData);
			double nextValue = result.getData(0);

			temp[i + network.getInput()] = nextValue;
			res[i] = nextValue;
		}
		
		logger.debug(String.format("Predicted data: %s", Arrays.toString(res)));

		if (revertTransformation) {
			int lostValues = transformer.getInputLenForOutputLen(network.getInput()) - network.getInput();
			res = transformer.revert(Arrays.copyOfRange(data, data.length - lostValues, data.length), res, false);
			logger.debug(String.format("Reverted data: %s", Arrays.toString(res)));
		}

		return res;
	}

}
